#include "bp/bp_string.h"
#include "bp_utils.h"
#include "bp/bp_unicode.h"

struct _bp_u8string_t
{
	bp_char8_t *str;
	size_t length;
};

_BP_INLINE static size_t _bp_mb_strlen(const bp_mbchar_t *mbstr)
{
	return mbstr ? strlen((const char *)mbstr) : 0;
}

_BP_INLINE static size_t _bp_utf16_strlen(const bp_char16_t *u16str)
{
	return u16str ? wcslen((const wchar_t *)u16str) : 0;
}

static const size_t _BP_UTF8BOM_HEAD_LEN = 3;
_BP_INLINE static bp_bool _bp_is_utf8bom(const bp_char8_t *u8str, size_t size)
{
	return _BP_BOOL_CAST(u8str && size > _BP_UTF8BOM_HEAD_LEN 
		&& u8str[0] == 0xEF && u8str[1] == 0xBB && u8str[2] == 0xBF);
}

static bp_mbchar_t * _bp_mb_strcopy(const bp_mbchar_t *src, size_t size);
static bp_char16_t * _bp_utf16_strcopy(const bp_char16_t *src, size_t size);

static bp_char8_t * _bp_bytes2utf8_copy(const bp_byte_t *bytes, size_t bsize, size_t *u8size, bp_string_encode srcencode);
static bp_char8_t * _bp_mb2utf8_copy(const bp_mbchar_t *src, size_t mbsize, size_t *u8size, bp_string_encode srcencode);
static bp_char8_t * _bp_utf16toutf8_copy(const bp_char16_t *src, size_t u16size, size_t *u8size, bp_string_encode srcencode);
static bp_char16_t * _bp_mb2utf16_copy(const bp_mbchar_t *src, size_t mbsize, size_t *u16size, bp_string_encode srcencode);

#if _BP_PLATFORM == _BP_PLATFORM_WINDOW
_BP_INLINE static UINT _bp_win_mb_encode(bp_string_encode encode)
{
	return encode == bp_string_encode_utf8 ? CP_UTF8 : CP_ACP;
}

static bp_char16_t * _bp_win_mb2utf16(const bp_mbchar_t *mbstr, size_t mbsize, size_t *u16size, bp_string_encode encode);
static bp_char8_t * _bp_win_utf16toutf8(const bp_char16_t *u16str, size_t u16size, size_t *u8size);
#endif

_BP_EXPORT bp_u8string_t _BP_API bp_u8string_from_bytes(const bp_byte_t *bytes, size_t size, bp_string_encode encode)
{
	bp_u8string_t string = BP_ALLOC_STRUCT_ZERO(struct _bp_u8string_t);
	if (!string) 
	{
		return NULL;
	}

	if (!bytes || size == 0)
	{
		string->str = BP_ALLOC_ARRAY_ZERO(1, bp_char8_t);
		if (!string->str)
		{
			bp_free(string);
			return NULL;
		}

		string->length = 0;
	}
	else
	{
		string->str = _bp_mb2utf8_copy(bytes, size, &string->length, encode);
		if (!string->str)
		{
			bp_free(string);
			return NULL;
		}
	}
	
	return string;
}

_BP_EXPORT const bp_char8_t * _BP_API bp_u8string_get_cstr(const bp_u8string_t string)
{
	return string ? string->str : NULL;
}

_BP_EXPORT size_t _BP_API bp_u8string_length(const bp_u8string_t string)
{
	return string ? string->length : 0;
}

_BP_EXPORT bp_bool _BP_API bp_u8string_next_u8char(bp_u8string_t string, bp_string_enumerate_state *state)
{
	if (!string || !state )
	{
		return BP_FALSE;
	}

	if (!state->string.u8)
	{
		state->string.u8 = string;
		state->current.u8 = string->str;
	}

	if (state->string.u8 != string 
		|| state->current.u8 == NULL
		|| state->current.u8 < string->str || state->current.u8 > string->str + string->length)
	{
		return BP_FALSE;
	}

	if (state->current.u8 >= string->str + string->length)
	{
		state->end_of_string = BP_TRUE;
		return BP_TRUE;
	}

	state->current.u8 = bp_u8next(state->current.u8, string->length - (state->current.u8 - string->str), &state->charater);
	return _BP_BOOL_CAST(state->current.u8 != NULL);
}

_BP_EXPORT bp_bool _BP_API bp_u8string_next_codepoint(bp_u8string_t string, bp_string_enumerate_state *state)
{
	bp_char32_t u8char = 0;
	if (!bp_u8string_next_u8char(string, state))
	{
		return BP_FALSE;
	}

	if (state->end_of_string)
	{
		return BP_TRUE;
	}

	u8char = state->charater;
	if (!bp_u8next_codepoint((bp_char8_t *)&u8char, 4, &state->charater))
	{
		return BP_FALSE;
	}

	return BP_TRUE;
}

_BP_EXPORT void _BP_API bp_u8string_free(bp_u8string_t str)
{
	if (!str)
	{
		return;
	}

	bp_free(str->str);
	bp_free(str);
}

bp_mbchar_t * _bp_mb_strcopy(const bp_mbchar_t *src, size_t size)
{
	bp_mbchar_t *str = BP_ALLOC_ARRAY_ZERO(size + 1, bp_mbchar_t);
	if (!str)
	{
		return NULL;
	}

	if (src && size > 0)
	{
		memcpy(str, src, size * sizeof(bp_mbchar_t));
	}

	return str;
}

bp_char16_t * _bp_utf16_strcopy(const bp_char16_t *src, size_t size)
{
	bp_char16_t *str = BP_ALLOC_ARRAY_ZERO(size + 1, bp_char16_t);
	if (!str) 
	{
		return NULL;
	}

	if (src && size > 0)
	{
		memcpy(str, src, size * sizeof(bp_char16_t));
	}

	return str;
}

bp_char8_t * _bp_bytes2utf8_copy(const bp_byte_t *bytes, size_t bsize, size_t *u8size, bp_string_encode srcencode)
{
	if (srcencode == bp_string_encode_utf16le || srcencode == bp_string_encode_utf16be) {
		return _bp_utf16toutf8_copy((const bp_char16_t *)bytes, bsize / 2, u8size, srcencode);
	}
	else if (srcencode == bp_string_encode_utf8 || srcencode == bp_string_encode_ansi) {
		return _bp_mb2utf8_copy(bytes, bsize, u8size, srcencode);
	}
	else {
		_BP_SAFE_SET_PTR(u8size, 0);
		return _bp_mb_strcopy(NULL, 0);
	}
}

bp_char8_t * _bp_mb2utf8_copy(const bp_mbchar_t *mbstr, size_t mbsize, size_t *u8size, bp_string_encode srcencode)
{
	bp_char8_t *utf8str = NULL;
	bp_char16_t *utf16str = NULL;
	size_t u16size = 0;

	_BP_ASSERT(srcencode == bp_string_encode_ansi || srcencode == bp_string_encode_utf8);
	if (!mbstr || mbstr == 0)
	{
		utf8str = _bp_mb_strcopy(NULL, 0);
		if (utf8str)
		{
			_BP_SAFE_SET_PTR(u8size, 0);
		}
		return utf8str;
	}
	
	if (srcencode == bp_string_encode_utf8)
	{
		if (_bp_is_utf8bom(mbstr, mbsize))
		{
			mbstr += _BP_UTF8BOM_HEAD_LEN;
			mbsize -= _BP_UTF8BOM_HEAD_LEN;
		}

		utf8str = _bp_mb_strcopy(mbstr, mbsize);
		if (utf8str)
		{
			_BP_SAFE_SET_PTR(u8size, mbsize);
		}
		return utf8str;
	}
	else if (srcencode == bp_string_encode_ansi)
	{
		utf16str = _bp_win_mb2utf16(mbstr, mbsize, &u16size, srcencode);
		if (!utf16str)
		{
			return NULL;
		}

		utf8str = _bp_win_utf16toutf8(utf16str, u16size, u8size);
		bp_free(utf16str);
	}

	return utf8str;
}

bp_char8_t * _bp_utf16toutf8_copy(const bp_char16_t *u16, size_t u16size, size_t *u8size, bp_string_encode srcencode)
{
	bp_char8_t *utf8str = NULL;

	_BP_ASSERT(srcencode == bp_string_encode_utf16le || srcencode == bp_string_encode_utf16be);
	if (!u16 || u16size == 0) {
		utf8str = _bp_mb_strcopy(NULL, 0);
		if (utf8str)
		{
			_BP_SAFE_SET_PTR(u8size, 0);
		}
		return utf8str;
	}
	else
	{

	}

	return utf8str;
}

bp_char16_t * _bp_mb2utf16_copy(const bp_mbchar_t *mbstr, size_t mbsize, size_t *u16size, bp_string_encode srcencode)
{
	return _bp_win_mb2utf16(mbstr, mbsize, u16size, srcencode);
}

#if _BP_PLATFORM == _BP_PLATFORM_WINDOW
bp_char16_t * _bp_win_mb2utf16(const bp_mbchar_t *mbstr, size_t mbsize, size_t *u16size, bp_string_encode encode)
{
	bp_char16_t *u16str = NULL;
	int reqsize = 0;
	UINT codepage = _bp_win_mb_encode(encode);
	if (!mbstr || mbsize == 0)
	{
		u16str = _bp_utf16_strcopy(NULL, 0);
		if (u16str)
		{
			_BP_SAFE_SET_PTR(u16size, 0);
		}
		return u16str;
	}

	reqsize = MultiByteToWideChar(codepage, MB_ERR_INVALID_CHARS, (LPCTSTR)mbstr, mbsize, NULL, 0);
	if (reqsize <= 0)
	{
		return NULL;
	}

	u16str = BP_ALLOC_ARRAY_ZERO(reqsize + 1, bp_char16_t);
	if (!u16str)
	{
		return NULL;
	}

	reqsize = MultiByteToWideChar(codepage, MB_ERR_INVALID_CHARS, (LPCTSTR)mbstr, mbsize, (LPWSTR)u16str, reqsize);
	if (reqsize <= 0)
	{
		bp_free(u16str);
		return NULL;
	}

	_BP_SAFE_SET_PTR(u16size, reqsize);
	return u16str;
}

bp_char8_t * _bp_win_utf16toutf8(const bp_char16_t *u16str, size_t u16size, size_t *u8size)
{
	bp_char8_t *u8str = NULL;
	int reqsize = 0;
	if (!u16str || u16size == 0)
	{
		u8str = _bp_mb_strcopy(NULL, 0);
		if (u8str)
		{
			_BP_SAFE_SET_PTR(u8size, 0);
		}
		return u8str;
	}

	reqsize = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, (LPCWSTR)u16str, u16size, NULL, 0, NULL, NULL);
	if (reqsize <= 0)
	{
		return NULL;
	}

	u8str = BP_ALLOC_ARRAY_ZERO(reqsize + 1, bp_char8_t);
	if (!u8str) 
	{
		return NULL;
	}

	reqsize = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, (LPCWSTR)u16str, u16size, (LPSTR)u8str, reqsize, NULL, NULL);
	if (reqsize <= 0)
	{
		bp_free(u8str);
		return NULL;
	}

	_BP_SAFE_SET_PTR(u8size, reqsize);
	return u8str;
}
#endif